Jx.declare('Jx.event.Manager', {

	$singleton: true,

	$helpers: {

		'Str': 'Jx.lib.String',

		'Fn': 'Jx.lib.Function'

	},

	$actions: [

		'addOperation',

		'removeOperation',

		'purgeOperation',

		'runOperationCleanup'

	],

	__ref_EventOptionRe: /^\:(.+)$/,

	__ref_EventOperationClassNs: 'event.operation',

	__ref_DefaultEventOperationClass: 'event.operation.default',

	__ref_ListenerClass: 'event.listener.default',

	__callback_ParseListenerObject: function(n, v, o){

		if (o.hasOwnProperty(n)){

			// is option
			var m = n.match(this.re);

			if (m){

				this.options[ m[1] ] = v;

			} else if (Jx.isFunction(v)){

				this.names.push(n);

			}

		}

	},

	__run_ApplyOperationCleanup: function(){

		var obj = this.operations,

			list = this.operationList,

			Fn = this.__Fn,

			now =  Math.floor((new Date()).getTime() / 1000),

			removeAfterElapsed =  Math.floor(this.removeCompletedAfterElapsed / 1000);

		for(var operation, id, pid,  deferred, ll, remove, list = this.operationList, l = list.length; l--;){

			operation = obj[ id = list[l] ];

			remove = operation.destroyed;

			// check if operation is not destroyed;
			if (!remove){

				// check if there are pending intervals or timeouts
				deferred = operation.deferred;

				for(ll = deferred.length; ll--;){

					if (!Fn.isRunning(pid = deferred[ll])){

						deferred.splice(ll);

					}

				}

				// set complete
				if (!operation.getCompleted() && !deferred.length){

					operation.setCompleted();

				}

				// check if completed
				remove = operation.getCompleted() &&

					(operation.timeCompleted + removeAfterElapsed - now) < 0;

			}

			// remove operation
			if ( remove ){

				obj[ id ].destroy();

				list.splice(l, 1);

				delete obj[id];

			}

		}

		// stop if everything is removed
		if (!list.length){

			Fn.stop( this.operationCleanupId );

			delete this.operationCleanupId;

		}

	},

	operationIdPrefix: 'event-operation',

	operations: undefined,

	operationList: undefined,

	operationCleanupId: undefined,

	operationCleanupDuration: 1000,

	removeCompletedAfterElapsed: 1000,

	constructor: function(){

		this.operations = {};

		this.operationList = [];

		this.$super(arguments);

	},

	allowAddOperation: function(o, type){

		var className = this.resolveOperationClass(type),

			params = o.params;

		o.valid = !!(className);

		params[1] = className;

		params[2] = type;

	},

	onAddOperation: function(o, className, type){

		o.execAfter = true;

		var J = Jx,

			operation = o.returnValue = J.instantiate(className, [

				J.isString(type) ?

					type
					:

					className

			]),

			list = this.operationList,

			id;

		this.operations[

			list[list.length] = id = operation.id = this.__Str.createId(this.operationIdPrefix)

		] = operation;



	},

	onAfterAddOperation: function(){

		this.runOperationCleanup();

	},

	allowRemoveOperation: function(o, operation){

		var J = Jx,

			id = J.isNumber(operation) ?

				operation

				:

				J.instanceOf(operation, this.__ref_DefaultEventOperationClass) ?

					operation.id

					:

					0,

			params = o.params,

			list = this.operationList,

			obj = this.operations,

			valid = o.valid = id in obj;

		params[1] = id;

		if (valid){

			params[2] = list.lastIndexOf(id);

			params[3] = list;

			params[4] = obj;

		}

	},

	onRemoveOperation: function(o, id, index, obj, list){

		o.execAfter = true;

		list.splice(index, 1);

		obj[ id ].destroy();

		delete obj[ id ];

	},

	onAfterRemoveOperation: function(o, id, list){

		this.__Str.removeId(id);

	},

	allowRunOperationCleanup: function(o){

		o.valid = !this.operationCleanupId && this.operationList.length;

	},

	onRunOperationCleanup: function(o){

		this.operationCleanupId = this.__Fn.interval(

			this.operationCleanupDuration,

			this.__run_ApplyOperationCleanup,

			this

		);

	},

	isOperationRemovable: function(id){

		var list = this.operations;

			operation;

		if (list.hasOwnProperty(id)){

			operation = list[id];


		}

		return false;

	},

	resolveOperationClass: function(type){

		var J = Jx;

		return J.resolveClassName(

			J.isString(type) && this.__ref_DefaultEventOperationClass+'.'+type,

			this.__ref_DefaultEventOperationClass

		);

	},

	parseListeners: function(args){

		var parsed = [],

			J = Jx,

			len;

		if (args &&

			(len = (J.isArray(args) ?

				args

				:

				args = Array.prototype.slice.call(args, 0)

			).length) >= 1

		){

			var subject = args[0],

				options, scope;

			// for argument format eventname, eventhandler, eventscope, eventoptions
			if (J.isString(subject) && len > 1){

				scope = len > 2 ? args[2] : void(0);

				parsed.push([

					subject,

					args[1],

					typeof scope == 'undefined' ?

						null

						:

						scope,

					len > 3 && J.isObject(options = args[3]) ?

						options

						:

						{}

				]);

			} else {

				if (J.isObject(subject)){

					subject = [subject];

				}

				if (J.isArray(subject)){

					for(var eventnames, ll, name, index, eventConfig, re = this.__ref_EventOptionRe, l = subject.length; l--;){

						options = {};

						eventnames = [];

						eventConfig = subject[l];

						J.each( eventConfig, this.__callback_ParseListenerObject, { options: options, names: eventnames, re: re });

						scope = options.scope;

						delete options.scope;

						for(ll = eventnames.length; ll--;){

							name = eventnames[ll];

							parsed.push([

								name,

								eventConfig[ name ],

								typeof scope == 'undefined' ?

									null

									:

									scope,

								options

							]);

						}

					}

				}

			}

		}

		return parsed;

	}



});
